<template>
  <div class="flow_region">
    <div class="nodes-wrap">
      <div
        v-for="item in nodeTypeList"
        :key="item.type"
        class="node"
        draggable="true"
        @dragstart="drag($event, item)"
      >
        <div class="log">
          <img :src="item.logImg" alt="" />
        </div>
        <div class="name">{{ item.typeName }}</div>
      </div>
    </div>
    <div
      id="flowWrap"
      ref="flowWrap"
      class="flow-wrap"
      @drop="drop($event)"
      @dragover="allowDrop($event)"
    >
      <div id="flow">
        <div
          v-show="auxiliaryLine.isShowXLine"
          class="auxiliary-line-x"
          :style="{
            width: auxiliaryLinePos.width,
            top: auxiliaryLinePos.y + 'px',
            left: auxiliaryLinePos.offsetX + 'px',
          }"
        ></div>
        <div
          v-show="auxiliaryLine.isShowYLine"
          class="auxiliary-line-y"
          :style="{
            height: auxiliaryLinePos.height,
            left: auxiliaryLinePos.x + 'px',
            top: auxiliaryLinePos.offsetY + 'px',
          }"
        ></div>
        <flowNode
          v-for="item in dataInit.nodeList"
          :id="item.id"
          :key="item.id"
          :node="item"
          @setNodeName="setNodeName"
          @deleteNode="deleteNode"
          @changeLineState="changeLineState"
        ></flowNode>
      </div>
    </div>
  </div>
</template>

<script>
import { jsPlumb } from "jsplumb";
import panzoom from "panzoom";
import { nodeTypeList } from "../config/init"; //左侧初始化数据
import {
  jsplumbSetting,
  jsplumbConnectOptions,
  jsplumbSourceOptions,
  jsplumbTargetOptions,
} from "../config/commonConfig";
import data from "../config/data.json"; //初始化数据
import flowNode from "../components/node-item.vue";
import { ref, reactive, onMounted, nextTick } from "vue";
export default {
  components: {
    flowNode,
  },
  setup() {
    let jsPlumbRef = ref(null);
    let currentItem = ref(null);
    let nodeTypeObj = ref({});
    let dataInit = reactive({
      nodeList: [],
      lineList: [],
    });
    let flowWrap = ref(null); //画布容器
    let auxiliaryLinePos = reactive({
      width: "100%",
      height: "100%",
      offsetX: 0,
      offsetY: 0,
      x: 20,
      y: 20,
    });
    let auxiliaryLine = reactive({ isShowXLine: false, isShowYLine: false }); //对齐辅助线是否显示

    const initNodeTypeObj = () => {
      nodeTypeList.map((v) => {
        nodeTypeObj.value[v.type] = v;
      });
    };
    const initNode = () => {
      dataInit.lineList = data.lineList;
      data.nodeList.map((v) => {
        v.logImg = nodeTypeObj.value[v.type].logImg;
        v.log_bg_color = nodeTypeObj.value[v.type].log_bg_color;
        dataInit.nodeList.push(v);
      });
    };

    //生成指定长度的唯一ID
    let GenNonDuplicateID = (randomLength) => {
      return Number(
        Math.random().toString().substr(3, randomLength) + Date.now()
      ).toString(36);
    };

    // methods
    const methods = {
      init() {
        jsPlumbRef.value.ready(() => {
          // 导入默认配置
          jsPlumbRef.value.importDefaults(jsplumbSetting);
          //完成连线前的校验
          jsPlumbRef.value.bind("beforeDrop", (evt) => {
            let res = () => {}; //此处可以添加是否创建连接的校验， 返回 false 则不添加；
            return res;
          });
          // 连线创建成功后，维护本地数据
          jsPlumbRef.value.bind("connection", (evt) => {
            methods.addLine(evt);
          });
          //连线双击删除事件
          jsPlumbRef.value.bind("dblclick", (conn, originalEvent) => {
            methods.confirmDelLine(conn);
          });
          //断开连线后，维护本地数据
          jsPlumbRef.value.bind("connectionDetached", (evt) => {
            methods.deleLine(evt);
          });
          methods.loadEasyFlow();
          // 会使整个jsPlumb立即重绘。
          jsPlumbRef.value.setSuspendDrawing(false, true);
        });
        methods.initPanZoom();
      },
      // 加载流程图
      loadEasyFlow() {
        // 初始化节点
        for (let i = 0; i < dataInit.nodeList.length; i++) {
          let node = dataInit.nodeList[i];
          // 设置源点，可以拖出线连接其他节点
          jsPlumbRef.value.makeSource(node.id, this.jsplumbSourceOptions);
          // // 设置目标点，其他源点拖出的线可以连接该节点
          jsPlumbRef.value.makeTarget(node.id, this.jsplumbTargetOptions);
          // jsPlumbRef.value.draggable(node.id);
          this.draggableNode(node.id);
        }

        // 初始化连线
        jsPlumbRef.value.unbind("connection"); //取消连接事件
        for (let i = 0; i < dataInit.lineList.length; i++) {
          let line = dataInit.lineList[i];
          jsPlumbRef.value.connect(
            {
              source: line.from,
              target: line.to,
            },
            this.jsplumbConnectOptions
          );
        }
        jsPlumbRef.value.bind("connection", (evt) => {
          let from = evt.source.id;
          let to = evt.target.id;
          dataInit.lineList.push({
            from: from,
            to: to,
            label: "连线名称",
            id: GenNonDuplicateID(8),
            Remark: "",
          });
        });
      },
      draggableNode(nodeId) {
        jsPlumbRef.value.draggable(nodeId, {
          grid: this.commonGrid,
          drag: (params) => {
            this.alignForLine(nodeId, params.pos);
          },
          start: () => {},
          stop: (params) => {
            this.auxiliaryLine.isShowXLine = false;
            this.auxiliaryLine.isShowYLine = false;
            this.changeNodePosition(nodeId, params.pos);
          },
        });
      },
      //移动节点时，动态显示对齐线
      alignForLine(nodeId, position) {
        let showXLine = false,
          showYLine = false;
        dataInit.nodeList.some((el) => {
          if (el.id !== nodeId && el.left == position[0] + "px") {
            auxiliaryLinePos.x = position[0] + 60;
            showYLine = true;
          }
          if (el.id !== nodeId && el.top == position[1] + "px") {
            auxiliaryLinePos.y = position[1] + 20;
            showXLine = true;
          }
        });
        this.auxiliaryLine.isShowYLine = showYLine;
        this.auxiliaryLine.isShowXLine = showXLine;
      },
      changeNodePosition(nodeId, pos) {
        dataInit.nodeList.some((v) => {
          if (nodeId == v.id) {
            v.left = pos[0] + "px";
            v.top = pos[1] + "px";
            return true;
          } else {
            return false;
          }
        });
      },
      drag(ele, item) {
        this.currentItem = item;
      },
      drop(event) {
        const containerRect = jsPlumbRef.value
          .getContainer()
          .getBoundingClientRect();
        const scale = this.getScale();
        let left = (event.pageX - containerRect.left - 60) / scale;
        let top = (event.pageY - containerRect.top - 20) / scale;

        var temp = {
          ...this.currentItem,
          id: GenNonDuplicateID(8),
          top: Math.round(top / 20) * 20 + "px",
          left: Math.round(left / 20) * 20 + "px",
        };
        this.addNode(temp);
      },
      addLine(line) {
        let from = line.source.id;
        let to = line.target.id;
        dataInit.lineList.push({
          from: from,
          to: to,
          label: "连线名称",
          id: GenNonDuplicateID(8),
          Remark: "",
        });
      },
      confirmDelLine(line) {
        this.$Modal.confirm({
          title: "删除连线",
          content: "<p>确认删除该连线？</p>",
          onOk: () => {
            jsPlumbRef.value.deleteConnection(line);
          },
        });
      },
      deleLine(line) {
        dataInit.lineList.forEach((item, index) => {
          if (item.from === line.sourceId && item.to === line.targetId) {
            dataInit.lineList.splice(index, 1);
          }
        });
      },
      // dragover默认事件就是不触发drag事件，取消默认事件后，才会触发drag事件
      allowDrop(event) {
        event.preventDefault();
      },
      getScale() {
        let scale1;
        if (jsPlumbRef.value.pan) {
          const { scale } = jsPlumbRef.value.pan.getTransform();
          scale1 = scale;
        } else {
          const matrix = window.getComputedStyle(
            jsPlumbRef.value.getContainer()
          ).transform;
          scale1 = matrix.split(", ")[3] * 1;
        }
        jsPlumbRef.value.setZoom(scale1);
        return scale1;
      },
      // 添加新的节点
      addNode(temp) {
        dataInit.nodeList.push(temp);
        this.$nextTick(() => {
          jsPlumbRef.value.makeSource(temp.id, this.jsplumbSourceOptions);
          jsPlumbRef.value.makeTarget(temp.id, this.jsplumbTargetOptions);
          this.draggableNode(temp.id);
        });
      },

      initPanZoom() {
        const mainContainer = jsPlumbRef.value.getContainer();
        const mainContainerWrap = mainContainer.parentNode;
        const pan = panzoom(mainContainer, {
          smoothScroll: false,
          bounds: true,
          // autocenter: true,
          zoomDoubleClickSpeed: 1,
          minZoom: 0.5,
          maxZoom: 2,
          //设置滚动缩放的组合键，默认不需要组合键
          beforeWheel: (e) => {
            console.log(e);
            // let shouldIgnore = !e.ctrlKey
            // return shouldIgnore
          },
          beforeMouseDown: function (e) {
            // allow mouse-down panning only if altKey is down. Otherwise - ignore
            var shouldIgnore = e.ctrlKey;
            return shouldIgnore;
          },
        });
        jsPlumbRef.value.mainContainerWrap = mainContainerWrap;
        jsPlumbRef.value.pan = pan;
        // 缩放时设置jsPlumb的缩放比率
        pan.on("zoom", (e) => {
          const { x, y, scale } = e.getTransform();
          jsPlumbRef.value.setZoom(scale);
          //根据缩放比例，缩放对齐辅助线长度和位置
          auxiliaryLinePos.width = (1 / scale) * 100 + "%";
          auxiliaryLinePos.height = (1 / scale) * 100 + "%";
          auxiliaryLinePos.offsetX = -(x / scale);
          auxiliaryLinePos.offsetY = -(y / scale);
        });
        pan.on("panend", (e) => {
          const { x, y, scale } = e.getTransform();
          auxiliaryLinePos.width = (1 / scale) * 100 + "%";
          auxiliaryLinePos.height = (1 / scale) * 100 + "%";
          auxiliaryLinePos.offsetX = -(x / scale);
          auxiliaryLinePos.offsetY = -(y / scale);
        });

        // 平移时设置鼠标样式
        mainContainerWrap.style.cursor = "grab";
        mainContainerWrap.addEventListener(
          "mousedown",
          function wrapMousedown() {
            this.style.cursor = "grabbing";
            mainContainerWrap.addEventListener(
              "mouseout",
              function wrapMouseout() {
                this.style.cursor = "grab";
              }
            );
          }
        );
        mainContainerWrap.addEventListener("mouseup", function wrapMouseup() {
          this.style.cursor = "grab";
        });
      },

      setNodeName(nodeId, name) {
        dataInit.nodeList.some((v) => {
          if (v.id === nodeId) {
            v.nodeName = name;
            return true;
          } else {
            return false;
          }
        });
      },

      //删除节点
      deleteNode(node) {
        dataInit.nodeList.some((v, index) => {
          if (v.id === node.id) {
            dataInit.nodeList.splice(index, 1);
            jsPlumbRef.value.remove(v.id);
            return true;
          } else {
            return false;
          }
        });
      },

      //更改连线状态
      changeLineState(nodeId, val) {
        console.log(val);
        let lines = jsPlumbRef.value.getAllConnections();
        lines.forEach((line) => {
          if (line.targetId === nodeId || line.sourceId === nodeId) {
            if (val) {
              line.canvas.classList.add("active");
            } else {
              line.canvas.classList.remove("active");
            }
          }
        });
      },

      //初始化节点位置  （以便对齐,居中）
      fixNodesPosition() {
        if (dataInit.nodeList && flowWrap.value) {
          const nodeWidth = 120;
          const nodeHeight = 40;
          let wrapInfo = flowWrap.value.getBoundingClientRect();
          let maxLeft = 0,
            minLeft = wrapInfo.width,
            maxTop = 0,
            minTop = wrapInfo.height;
          let nodePoint = {
            left: 0,
            right: 0,
            top: 0,
            bottom: 0,
          };
          let fixTop = 0,
            fixLeft = 0;
          dataInit.nodeList.forEach((el) => {
            let top = Number(el.top.substring(0, el.top.length - 2));
            let left = Number(el.left.substring(0, el.left.length - 2));
            maxLeft = left > maxLeft ? left : maxLeft;
            minLeft = left < minLeft ? left : minLeft;
            maxTop = top > maxTop ? top : maxTop;
            minTop = top < minTop ? top : minTop;
          });
          nodePoint.left = minLeft;
          nodePoint.right = wrapInfo.width - maxLeft - nodeWidth;
          nodePoint.top = minTop;
          nodePoint.bottom = wrapInfo.height - maxTop - nodeHeight;

          fixTop =
            nodePoint.top !== nodePoint.bottom
              ? (nodePoint.bottom - nodePoint.top) / 2
              : 0;
          fixLeft =
            nodePoint.left !== nodePoint.right
              ? (nodePoint.right - nodePoint.left) / 2
              : 0;

          dataInit.nodeList.map((el) => {
            let top = Number(el.top.substring(0, el.top.length - 2)) + fixTop;
            let left =
              Number(el.left.substring(0, el.left.length - 2)) + fixLeft;
            el.top = Math.round(top / 20) * 20 + "px";
            el.left = Math.round(left / 20) * 20 + "px";
          });
        }
      },
    };

    onMounted(() => {
      jsPlumbRef.value = jsPlumb.getInstance();
      initNodeTypeObj();
      initNode();
      methods.fixNodesPosition();
      nextTick(() => {
        methods.init();
      });
    });

    return {
      ...methods,
      nodeTypeList,
      jsPlumb,
      currentItem,
      nodeTypeObj,
      dataInit,
      flowWrap,
      auxiliaryLinePos,
      auxiliaryLine,
    };
  },
};
</script>

<style lang="less" scoped>
.flow_region {
  display: flex;
  width: 90%;
  // height: 90%;
  height: 500px;
  margin: 20px auto;
  border: 1px solid #ccc;
  .nodes-wrap {
    width: 150px;
    height: 100%;
    border-right: 1px solid #ccc;
    .node {
      display: flex;
      height: 40px;
      width: 80%;
      margin: 5px auto;
      border: 1px solid #ccc;
      line-height: 40px;
      &:hover {
        cursor: grab;
      }
      &:active {
        cursor: grabbing;
      }
      .log {
        width: 40px;
        height: 40px;
      }
      .name {
        width: 0;
        flex-grow: 1;
      }
    }
  }
  .flow-wrap {
    height: 100%;
    position: relative;
    overflow: hidden;
    outline: none !important;
    flex-grow: 1;
    background-image: url("../assets/point.png");
    #flow {
      position: relative;
      width: 100%;
      height: 100%;
      .auxiliary-line-x {
        position: absolute;
        border: 0.5px dashed #2ab1e8;
        z-index: 9999;
      }
      .auxiliary-line-y {
        position: absolute;
        border: 0.5px dashed #2ab1e8;
        z-index: 9999;
      }
    }
  }
}
</style>

<style lang="less">
.jtk-connector.active {
  z-index: 9999;
  path {
    stroke: #150042;
    stroke-width: 1.5;
    animation: ring;
    animation-duration: 3s;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
    stroke-dasharray: 5;
  }
}
@keyframes ring {
  from {
    stroke-dashoffset: 50;
  }
  to {
    stroke-dashoffset: 0;
  }
}
</style>